package database

import (
	"context"
	"fmt"
	"log"
	"time"

	"iot-cloud-platform/internal/config"
	"iot-cloud-platform/internal/models"

	"github.com/google/uuid"
	influxdb2 "github.com/influxdata/influxdb-client-go/v2"
	"github.com/influxdata/influxdb-client-go/v2/api"
	"github.com/influxdata/influxdb-client-go/v2/api/write"
)

// InfluxManager provides high-level InfluxDB operations for time-series data
type InfluxManager struct {
	client   influxdb2.Client
	writeAPI api.WriteAPI
	queryAPI api.QueryAPI
	org      string
	bucket   string
}

// InitInfluxDB initializes InfluxDB connection
func InitInfluxDB(cfg config.InfluxDBConfig) (*InfluxManager, error) {
	client := influxdb2.NewClient(cfg.URL, cfg.Token)

	// Test connection
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	health, err := client.Health(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to InfluxDB: %w", err)
	}

	if health.Status != "pass" {
		return nil, fmt.Errorf("InfluxDB health check failed: %s", health.Message)
	}

	writeAPI := client.WriteAPI(cfg.Org, cfg.Bucket)
	queryAPI := client.QueryAPI(cfg.Org)

	log.Println("InfluxDB connected successfully")

	return &InfluxManager{
		client:   client,
		writeAPI: writeAPI,
		queryAPI: queryAPI,
		org:      cfg.Org,
		bucket:   cfg.Bucket,
	}, nil
}

// WriteTelemetryData writes telemetry data to InfluxDB
func (i *InfluxManager) WriteTelemetryData(ctx context.Context, data models.TelemetryData) error {
	point := write.NewPoint("telemetry",
		map[string]string{
			"device_id": data.DeviceID.String(),
			"quality":   string(data.Quality),
		},
		data.Data,
		data.Timestamp)

	// Add location tags if available
	if data.Location != nil {
		point.AddTag("has_location", "true")
		point.AddField("latitude", data.Location.Latitude)
		point.AddField("longitude", data.Location.Longitude)
		point.AddField("altitude", data.Location.Altitude)
		if data.Location.Address != "" {
			point.AddTag("address", data.Location.Address)
		}
	}

	i.writeAPI.WritePoint(point)
	return nil
}

// WriteBatchTelemetryData writes multiple telemetry points efficiently
func (i *InfluxManager) WriteBatchTelemetryData(ctx context.Context, dataPoints []models.TelemetryData) error {
	points := make([]*write.Point, len(dataPoints))

	for idx, data := range dataPoints {
		point := write.NewPoint("telemetry",
			map[string]string{
				"device_id": data.DeviceID.String(),
				"quality":   string(data.Quality),
			},
			data.Data,
			data.Timestamp)

		if data.Location != nil {
			point.AddTag("has_location", "true")
			point.AddField("latitude", data.Location.Latitude)
			point.AddField("longitude", data.Location.Longitude)
			point.AddField("altitude", data.Location.Altitude)
			if data.Location.Address != "" {
				point.AddTag("address", data.Location.Address)
			}
		}

		points[idx] = point
	}

	for _, point := range points {
		i.writeAPI.WritePoint(point)
	}
	return nil
}

// QueryTelemetryData queries telemetry data for a device within a time range
func (i *InfluxManager) QueryTelemetryData(ctx context.Context, deviceID uuid.UUID, start, end time.Time, limit int) ([]models.TelemetryData, error) {
	query := fmt.Sprintf(`
		from(bucket: "%s")
		|> range(start: %s, stop: %s)
		|> filter(fn: (r) => r._measurement == "telemetry")
		|> filter(fn: (r) => r.device_id == "%s")
		|> sort(columns: ["_time"], desc: true)
		|> limit(n: %d)
	`, i.bucket, start.Format(time.RFC3339), end.Format(time.RFC3339), deviceID.String(), limit)

	result, err := i.queryAPI.Query(ctx, query)
	if err != nil {
		return nil, fmt.Errorf("failed to query telemetry data: %w", err)
	}
	defer result.Close()

	var telemetryData []models.TelemetryData
	pointsMap := make(map[time.Time]*models.TelemetryData)

	for result.Next() {
		record := result.Record()
		timestamp := record.Time()

		if pointsMap[timestamp] == nil {
			pointsMap[timestamp] = &models.TelemetryData{
				DeviceID:  deviceID,
				Timestamp: timestamp,
				Data:      make(map[string]interface{}),
				Quality:   models.DataQuality(record.ValueByKey("quality").(string)),
			}
		}

		point := pointsMap[timestamp]
		fieldName := record.Field()
		fieldValue := record.Value()

		// Handle location fields
		if fieldName == "latitude" || fieldName == "longitude" || fieldName == "altitude" {
			if point.Location == nil {
				point.Location = &models.Location{}
			}
			switch fieldName {
			case "latitude":
				point.Location.Latitude = fieldValue.(float64)
			case "longitude":
				point.Location.Longitude = fieldValue.(float64)
			case "altitude":
				point.Location.Altitude = fieldValue.(float64)
			}
		} else {
			point.Data[fieldName] = fieldValue
		}
	}

	if err := result.Err(); err != nil {
		return nil, fmt.Errorf("query result error: %w", err)
	}

	// Convert map to slice
	for _, point := range pointsMap {
		telemetryData = append(telemetryData, *point)
	}

	return telemetryData, nil
}

// QueryAggregatedData queries aggregated telemetry data
func (i *InfluxManager) QueryAggregatedData(ctx context.Context, deviceID uuid.UUID, field string, aggregation string, start, end time.Time, window string) ([]models.TelemetryMetrics, error) {
	query := fmt.Sprintf(`
		from(bucket: "%s")
		|> range(start: %s, stop: %s)
		|> filter(fn: (r) => r._measurement == "telemetry")
		|> filter(fn: (r) => r.device_id == "%s")
		|> filter(fn: (r) => r._field == "%s")
		|> aggregateWindow(every: %s, fn: %s, createEmpty: false)
		|> yield(name: "%s")
	`, i.bucket, start.Format(time.RFC3339), end.Format(time.RFC3339), deviceID.String(), field, window, aggregation, aggregation)

	result, err := i.queryAPI.Query(ctx, query)
	if err != nil {
		return nil, fmt.Errorf("failed to query aggregated data: %w", err)
	}
	defer result.Close()

	var metrics []models.TelemetryMetrics

	for result.Next() {
		record := result.Record()

		metric := models.TelemetryMetrics{
			DeviceID:   deviceID,
			Metric:     field,
			TimeWindow: window,
			StartTime:  start,
			EndTime:    end,
			Values: map[string]interface{}{
				aggregation: record.Value(),
			},
			Tags: map[string]string{
				"device_id": deviceID.String(),
			},
		}

		metrics = append(metrics, metric)
	}

	if err := result.Err(); err != nil {
		return nil, fmt.Errorf("aggregated query result error: %w", err)
	}

	return metrics, nil
}

// WriteSystemMetrics writes system health metrics
func (i *InfluxManager) WriteSystemMetrics(ctx context.Context, metrics models.SystemMetrics) error {
	point := write.NewPoint("system_metrics",
		map[string]string{
			"source": "platform",
		},
		map[string]interface{}{
			"active_devices":   metrics.ActiveDevices,
			"total_devices":    metrics.TotalDevices,
			"messages_per_sec": metrics.MessagesPerSec,
			"active_users":     metrics.ActiveUsers,
			"system_load":      metrics.SystemLoad,
			"memory_usage":     metrics.MemoryUsage,
			"disk_usage":       metrics.DiskUsage,
			"network_traffic":  metrics.NetworkTraffic,
			"error_rate":       metrics.ErrorRate,
			"response_time":    metrics.ResponseTime,
		},
		metrics.Timestamp)

	i.writeAPI.WritePoint(point)
	return nil
}

// QuerySystemMetrics queries system metrics within a time range
func (i *InfluxManager) QuerySystemMetrics(ctx context.Context, start, end time.Time) ([]models.SystemMetrics, error) {
	query := fmt.Sprintf(`
		from(bucket: "%s")
		|> range(start: %s, stop: %s)
		|> filter(fn: (r) => r._measurement == "system_metrics")
		|> sort(columns: ["_time"], desc: true)
	`, i.bucket, start.Format(time.RFC3339), end.Format(time.RFC3339))

	result, err := i.queryAPI.Query(ctx, query)
	if err != nil {
		return nil, fmt.Errorf("failed to query system metrics: %w", err)
	}
	defer result.Close()

	metricsMap := make(map[time.Time]*models.SystemMetrics)

	for result.Next() {
		record := result.Record()
		timestamp := record.Time()

		if metricsMap[timestamp] == nil {
			metricsMap[timestamp] = &models.SystemMetrics{
				Timestamp: timestamp,
			}
		}

		metric := metricsMap[timestamp]
		fieldName := record.Field()
		fieldValue := record.Value()

		switch fieldName {
		case "active_devices":
			metric.ActiveDevices = fieldValue.(int64)
		case "total_devices":
			metric.TotalDevices = fieldValue.(int64)
		case "messages_per_sec":
			metric.MessagesPerSec = fieldValue.(float64)
		case "active_users":
			metric.ActiveUsers = fieldValue.(int64)
		case "system_load":
			metric.SystemLoad = fieldValue.(float64)
		case "memory_usage":
			metric.MemoryUsage = fieldValue.(float64)
		case "disk_usage":
			metric.DiskUsage = fieldValue.(float64)
		case "network_traffic":
			metric.NetworkTraffic = fieldValue.(float64)
		case "error_rate":
			metric.ErrorRate = fieldValue.(float64)
		case "response_time":
			metric.ResponseTime = fieldValue.(float64)
		}
	}

	if err := result.Err(); err != nil {
		return nil, fmt.Errorf("system metrics query result error: %w", err)
	}

	var metrics []models.SystemMetrics
	for _, metric := range metricsMap {
		metrics = append(metrics, *metric)
	}

	return metrics, nil
}

// Flush forces write API to flush all pending data
func (i *InfluxManager) Flush() {
	i.writeAPI.Flush()
}

// Close closes the InfluxDB client connection
func (i *InfluxManager) Close() {
	i.client.Close()
}
